package service

import (
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"os"
	"sync"
	"time"

	"gitee.com/night-tc/gobige/common"
	"gitee.com/night-tc/gobige/entity"
	"gitee.com/night-tc/gobige/etcd"
	"gitee.com/night-tc/gobige/global"
	"gitee.com/night-tc/gobige/logger"
	"gitee.com/night-tc/gobige/msgdef"
	"gitee.com/night-tc/gobige/msgdef/protomsg"
	"gitee.com/night-tc/gobige/msghandler"
	"gitee.com/night-tc/gobige/pool"
	"gitee.com/night-tc/gobige/redislib"
	"gitee.com/night-tc/gobige/serverMgr"
	"gitee.com/night-tc/gobige/sess"
	"gitee.com/night-tc/gobige/threads"
	"gitee.com/night-tc/gobige/timerwheel"
	"github.com/google/uuid"
	"go.uber.org/atomic"
)

type optionF func(*Service)

// 初始化网络服务
func SrvSetNetSrv(v *sess.NetSrv) optionF {
	return func(s *Service) {
		s.NetSrv = v
	}
}

// 初始化对象管理器
func SrvSetEntities(v *entity.Entities) optionF {
	return func(s *Service) {
		s.Entities = v
	}
}

// 初始化定时器
func SrvSetTimer(v *timerwheel.Timer) optionF {
	return func(s *Service) {
		s.Timer = v
	}
}

// 设置是否有玩家协程
func SrvSetIsHavePlayer(v bool) optionF {
	return func(s *Service) {
		s.IsHavePlayer = v
	}
}

/*
服务器基类
*/
type Service struct {
	entity.DelayCallHost
	*sess.NetSrv
	*entity.Entities
	*timerwheel.Timer

	//上层引用
	real IServiceReal

	// eid生成器
	eIDGenerater *IDGenerater
	//协程管理
	thgo *threads.ThreadGo
	//服务器关闭信号
	closeC chan os.Signal
	//每次Tick的时候的当前时间
	NowTime atomic.Time
	//时间偏移步长
	TimeStep time.Duration
	ticker   *time.Ticker

	//每个服务器启动的时候自动生成的值
	Token string
	// 服务器状态
	Status atomic.Int32

	//主动连接其他服务器的连接
	pendingSesses sync.Map
	clientSesses  sync.Map

	//服务器信息
	Sinfo *global.GlobalConfig
	// 监听地址
	ListenMap map[string]string

	//etcd
	Etcd *etcd.EtcdAccess
	//服务器信息管理器
	DisMgr *serverMgr.DiscoveryMgr

	// 所有等回复的消息
	WaitList sync.Map
	// 是否有玩家协程 默认为true
	IsHavePlayer bool
}

var (
	_ entity.IEntities = &Service{}
)

func NewService(sinfo *global.GlobalConfig, real IServiceReal, opts ...optionF) (result *Service) {
	result = new(Service)
	if real == nil {
		result.real = result
	} else {
		result.real = real
	}
	//设置服务器实例
	global.SetSrvInst(result.real)
	result.thgo = threads.NewThreadGo()
	// logger.Init(path.Join(common.GetResPath(), sinfo.LogFile), sinfo.SrvName, nil)

	result.eIDGenerater = NewIDGenerater(sinfo.ServerID)
	result.Sinfo = sinfo
	result.ListenMap = make(map[string]string)
	result.closeC = make(chan os.Signal, 1)
	result.Token = uuid.New().String()
	result.NowTime.Store(time.Now())
	result.IsHavePlayer = false
	//这三个组合器需要在上层初始话
	// result.NetSrv = sess.NewNetServer("", result.thgo, real)
	// result.Entities = entity.NewEntities(true, real, result.thgo)
	// result.Timer = timerwheel.NewTimer()

	for _, opt := range opts {
		opt(result)
	}
	if result.NetSrv == nil {
		if v, ok := sinfo.ListenMap[global.ListenType_Inner]; ok {
			result.NetSrv = sess.NewNetServer(v, result.thgo, result.real)
			// 实现接口就设置
			if v, ok := result.real.(msghandler.IMsgOwner); ok {
				result.RegMsgOwner(v)
			}
			result.ListenMap[global.ListenType_Inner] = result.NetSrv.ListenAddr
			if sinfo.Debug {
				l, p := sess.GetListener("tcp", v)
				sinfo.DebugAddr = fmt.Sprintf("%s:%d", v.Addr, p)
				result.ListenMap["DebugAddr"] = sinfo.DebugAddr
				logger.Infof("监听Pprof端口 : http://%v/debug/pprof", sinfo.DebugAddr)
				go func() {
					_ = http.Serve(l, nil)
				}()
			}
		} else {
			panic("no inner listen")
			//result.NetSrv = sess.NewNetServer(nil, result.thgo, result.real)
		}

	}
	if result.Timer == nil {
		result.Timer = timerwheel.NewTimer()
	}
	if result.Etcd == nil {
		var err error
		result.Etcd, err = etcd.NewEtcdAccess(
			etcd.SetEtcdAddrs(sinfo.EtcdCfg.Addrs...),
			etcd.SetEtcdDialTimeout(sinfo.EtcdCfg.DialTimeout),
			etcd.SetEtcdLeaseTTL(sinfo.EtcdCfg.LeaseTTL),
		)
		if err != nil {
			panic(err)
		}
	}
	if result.Entities == nil {
		result.Entities = entity.NewEntities(true, result.real, result.thgo)
	}
	if result.DisMgr == nil {
		result.DisMgr = serverMgr.NewDiscoveryMgr(result.thgo, result.real)
	}
	//实例化redis
	for name, v := range sinfo.RedisMap {
		rd := redislib.NewRedisAccess(
			redislib.RedisSetAddr(v.Addr),
			redislib.RedisSetAuth(v.Password),
			redislib.RedisSetIdleTimeout(time.Duration(v.IdleTimeout)*time.Second),
			redislib.RedisSetIndexDB(v.Index),
			redislib.RedisSetMaxActive(v.MaxActive),
			redislib.RedisSetMaxIdle(v.MaxIdle),
		)
		global.SetRedisByName(name, rd)

	}
	result.ticker = time.NewTicker(result.Sinfo.FrameDeltaTime)
	return
}

// ////////////////////////////////////////////////////////////
// 主协程调用
func (this *Service) Run() {
	/*
		这个函数在主协程上运行

		一些需要启动的管理器

		这个函数不能panic所以调用上层代码的时候，都需要有recover的保护

	*/
	// global.PrintConfig()
	logger.Infof("\n BuildTime : %s\n Version : %s\n Gitcommit : %s", global.Built, global.Version, global.Gitcommit)

	if err := this.real.Init(); err != nil {
		logger.Errorf("Service.Run err:%v", err)
		return
	}
	logger.Infof("Service %s type:%d id:%d group:%d Runing.",
		this.Sinfo.SrvName, this.Sinfo.SrvType, this.Sinfo.ServerID, this.Sinfo.GroupID)
	SetTitle(fmt.Sprintf("%s;G:%d;ID:%d;No:%d", this.Sinfo.SrvName, this.Sinfo.GroupID, this.Sinfo.ServerID, this.Sinfo.Number))
	logger.Info(common.LOGO)
	defer this.ticker.Stop()
	for {
		select {
		case <-this.closeC:
			//关服
			logger.Infof("Service %s type:%d id:%d group:%d Stoping.",
				this.Sinfo.SrvName, this.Sinfo.SrvType, this.Sinfo.ServerID, this.Sinfo.GroupID)
			this.real.Destroy()
			logger.Info(common.Stop)
			return
		case <-this.GetCallChan():
			this.real.DoMsg()
		case nowtime := <-this.ticker.C:
			this.NowTime.Store(nowtime.Add(this.TimeStep))
			this.real.DoMsg()
			this.mainLoop()
		}
	}
}

//////////////////////////////////////////////////////////////
/* 私有函数 */

func (this *Service) mainLoop() {
	defer func() {
		if global.GetCfgInst().GetRecover() {
			if err := recover(); err != nil {
				logger.Error(err)
				buf := common.StackTrace()
				common.OnRecover(err, buf, this)

			}
		}
	}()
	this.real.Loop()
	this.real.Looping()
	this.real.Looped()
}

// 打包成转发MSG消息到目标服务器，这个消息只会在service上处理
func (this *Service) packSrvMsg(stype uint32, srvid, spid, eid uint64, msg msgdef.IMsg) (result *protomsg.ForwardMsgSrv, err error) {
	result = new(protomsg.ForwardMsgSrv)
	result.EntityID = eid
	result.SpaceID = spid
	result.SrvID = srvid
	result.SrvType = stype
	buf := pool.Get(sess.MaxMsgBuffer)
	var encBuf []byte
	if stype == global.ServerTypeClient {
		encBuf, err = sess.EncodeMsgWithEncrypt(msg, buf, true, this.Sinfo.EncryptEnabled)
	} else {
		encBuf, err = sess.EncodeMsg(msg, buf, true)
	}
	if err != nil {
		return nil, err
	}
	msgContent := make([]byte, len(encBuf))
	copy(msgContent, encBuf)
	pool.Put(buf)
	result.MsgContent = msgContent
	return
}

// 打包成转发MSG消息到目标服务器，这个消息只会在service上处理
func (this *Service) packBufSrvMsg(stype uint32, srvid, spid, eid uint64, msgContent []byte) (result *protomsg.ForwardMsgSrv, err error) {
	result = new(protomsg.ForwardMsgSrv)
	result.EntityID = eid
	result.SpaceID = spid
	result.SrvID = srvid
	result.SrvType = stype
	result.MsgContent = msgContent
	return
}

/* 私有函数 */
//////////////////////////////////////////////////////////////
/* 接口函数 */

func (this *Service) String() string {
	return this.Entities.String()
}

/* 接口函数 */
//////////////////////////////////////////////////////////////
/* 定时器 */

// 定时判断服务器是否需要关闭
func (this *Service) CheckClose(...interface{}) {
	// 服务器关闭
	st := protomsg.ServerStatus(this.Status.Load())
	switch st {
	case protomsg.ServerStatus_Maintain:
		// 维护状态，直接关闭
		this.closeC <- os.Interrupt
	case protomsg.ServerStatus_Gray:
		// 灰度状态，判断是否需要关闭
		// 找到有玩家，就不退出了
		isclose := true
		this.Range(func(k, v interface{}) bool {
			switch ie := v.(type) {
			case entity.IEntities:
				ie.Range(func(k, v interface{}) bool {
					switch e := v.(type) {
					case entity.IEntity:
						if e.GetType() == common.EntityType_Player {
							isclose = false
							return false
						}
					}
					return true
				})
			case entity.IEntity:
				if ie.GetType() == common.EntityType_Player {
					isclose = false
					return false
				}
			}
			return true
		})
		if isclose {
			this.closeC <- os.Interrupt
		}
	}

}

/* 定时器 */
//////////////////////////////////////////////////////////////
